"""
密码配置和验证模块
"""
import os
import bcrypt
import re
from datetime import datetime, timedelta
from app.utils.log_control import ERROR, INFO
from app.utils.db_utils import DatabaseConnection
from app.config.sql_config import LOGIN_ATTEMPTS_SQL

# 密码复杂度要求
PASSWORD_MIN_LENGTH = 5
PASSWORD_REQUIREMENTS = {
    'uppercase': r'[A-Z]',  # 大写字母
    'lowercase': r'[a-z]',  # 小写字母
    'numbers': r'[0-9]',  # 数字
    'special': r'[!@#$%^&*(),.?":{}|<>]'  # 特殊字符
}

# 从环境变量获取密码，如果没有则使用默认值
# 在生产环境中，应该强制要求设置环境变量
DEFAULT_PASSWORD = "10086"  # 更改为符合复杂度要求的默认密码
PASSWORD = os.environ.get('ADMIN_PASSWORD', DEFAULT_PASSWORD)

# 生成密码哈希
PASSWORD_HASH = bcrypt.hashpw(PASSWORD.encode('utf-8'), bcrypt.gensalt(rounds=10))

# 登录尝试配置
MAX_ATTEMPTS = 5  # 最大尝试次数
LOCKOUT_DURATION = 5  # 锁定时间(分钟)
ATTEMPT_RESET_AFTER = 24  # 重置失败尝试的时间(小时)


def init_db():
    """初始化数据库表"""
    try:
        with DatabaseConnection() as db:
            # 创建登录尝试记录表
            db.execute(LOGIN_ATTEMPTS_SQL['create_table'])
            INFO.logger.info("登录尝试表初始化成功")
    except Exception as e:
        ERROR.logger.error(f"初始化数据库失败: {str(e)}")


def get_login_attempts(ip_address: str) -> dict:
    """获取登录尝试记录"""
    try:
        with DatabaseConnection() as db:
            result = db.execute_query(
                LOGIN_ATTEMPTS_SQL['get_attempts'],
                {'ip': ip_address}
            )

            if result and result.get('data') and len(result['data']) > 0:
                record = result['data'][0]
                return {
                    'attempts': record['attempts'],
                    'last_attempt': record['last_attempt'],
                    'first_attempt': record['first_attempt']
                }
            return None
    except Exception as e:
        ERROR.logger.error(f"获取登录尝试记录失败: {str(e)}")
        return None


def update_login_attempts_db(ip_address: str, is_valid: bool) -> int:
    """更新登录尝试记录"""
    try:
        with DatabaseConnection() as db:
            current_time = datetime.now()

            if not is_valid:
                # 获取当前尝试次数
                result = db.execute_query(
                    LOGIN_ATTEMPTS_SQL['get_current_attempts'],
                    {'ip': ip_address}
                )

                if result and result.get('data') and len(result['data']) > 0:
                    # 更新现有记录
                    new_attempts = result['data'][0]['attempts'] + 1
                    db.execute(
                        LOGIN_ATTEMPTS_SQL['update_attempts'],
                        {
                            'attempts': new_attempts,
                            'last_attempt': current_time,
                            'ip': ip_address
                        }
                    )
                    return MAX_ATTEMPTS - new_attempts
                else:
                    # 创建新记录
                    db.execute(
                        LOGIN_ATTEMPTS_SQL['insert_attempt'],
                        {
                            'ip': ip_address,
                            'time': current_time
                        }
                    )
                    return MAX_ATTEMPTS - 1
            else:
                # 重置尝试次数
                db.execute(
                    LOGIN_ATTEMPTS_SQL['reset_attempts'],
                    {
                        'time': current_time,
                        'ip': ip_address
                    }
                )
                return MAX_ATTEMPTS
    except Exception as e:
        ERROR.logger.error(f"更新登录尝试记录失败: {str(e)}")
        return 0


def is_locked_out(ip_address: str) -> bool:
    """检查IP是否被锁定"""
    try:
        attempts = get_login_attempts(ip_address)
        if attempts and attempts['attempts'] >= MAX_ATTEMPTS:
            lockout_time = attempts['last_attempt']
            if datetime.now() - lockout_time < timedelta(minutes=LOCKOUT_DURATION):
                return True
            else:
                # 锁定期已过，重置尝试次数
                update_login_attempts_db(ip_address, True)
        return False
    except Exception as e:
        ERROR.logger.error(f"检查锁定状态失败: {str(e)}")
        return False


def get_lockout_remaining_time(ip_address: str) -> int:
    """获取剩余锁定时间(分钟)"""
    try:
        attempts = get_login_attempts(ip_address)
        if attempts and attempts['attempts'] >= MAX_ATTEMPTS:
            lockout_time = attempts['last_attempt']
            elapsed = datetime.now() - lockout_time
            remaining = timedelta(minutes=LOCKOUT_DURATION) - elapsed
            return max(0, int(remaining.total_seconds() / 60))
        return 0
    except Exception as e:
        ERROR.logger.error(f"获取剩余锁定时间失败: {str(e)}")
        return 0


def cleanup_old_attempts():
    """清理过期的登录尝试记录"""
    try:
        with DatabaseConnection() as db:
            expired_time = datetime.now() - timedelta(hours=ATTEMPT_RESET_AFTER)

            db.execute(
                LOGIN_ATTEMPTS_SQL['cleanup_attempts'],
                {
                    'expired_time': expired_time,
                    'max_attempts': MAX_ATTEMPTS
                }
            )
            INFO.logger.info("清理过期登录尝试记录成功")
    except Exception as e:
        ERROR.logger.error(f"清理过期记录失败: {str(e)}")


def verify_password(password: str, ip_address: str) -> tuple[bool, str]:
    """
    验证密码
    
    Args:
        password: 待验证的密码
        ip_address: 客户端IP地址
        
    Returns:
        tuple[bool, str]: (验证是否成功, 错误信息)
    """
    try:
        # 确保数据库已初始化
        init_db()

        # 检查是否被锁定
        if is_locked_out(ip_address):
            remaining_time = get_lockout_remaining_time(ip_address)
            return False, f"由于多次失败尝试，账户已被锁定。请在{remaining_time}分钟后重试"

        # 验证密码
        is_valid = bcrypt.checkpw(password.encode('utf-8'), PASSWORD_HASH)

        # 更新尝试记录
        attempts_remaining = update_login_attempts_db(ip_address, is_valid)

        if is_valid:
            INFO.logger.info(f"密码验证成功 - IP: {ip_address}")
            cleanup_old_attempts()  # 清理过期记录
            return True, ""
        else:
            INFO.logger.warning(f"密码验证失败 - IP: {ip_address}")
            if attempts_remaining > 0:
                return False, f"密码错误，还剩{attempts_remaining}次尝试机会"
            else:
                return False, f"密码错误，账户已被锁定{LOCKOUT_DURATION}分钟"

    except Exception as e:
        ERROR.logger.error(f"密码验证过程发生错误: {str(e)}")
        return False, "系统错误，请稍后重试"


def check_password_strength(password: str) -> tuple[bool, str]:
    """
    检查密码强度
    
    Args:
        password: 待检查的密码
        
    Returns:
        tuple[bool, str]: (是否通过, 错误信息)
    """
    if len(password) < PASSWORD_MIN_LENGTH:
        return False, f"密码长度必须至少为{PASSWORD_MIN_LENGTH}个字符"

    missing_requirements = []
    for requirement, pattern in PASSWORD_REQUIREMENTS.items():
        if not re.search(pattern, password):
            missing_requirements.append(requirement)

    if missing_requirements:
        requirement_names = {
            'uppercase': '大写字母',
            'lowercase': '小写字母',
            'numbers': '数字',
            'special': '特殊字符'
        }
        missing = [requirement_names[req] for req in missing_requirements]
        return False, f"密码必须包含: {', '.join(missing)}"

    return True, ""
